package gui;

import data.ApplicationProperties;
import java.awt.Window;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import midi.MidiController;
import midi.MidiObserver;
import midi.MidiSubject;

/**
 * Class: TestWindow.java
 * Description: Manually configures the component given the requested
 * tests.
 * @author eric
 */
public class TestWindow extends javax.swing.JPanel
{
    private boolean testFinished;

    private boolean testHit;
    private boolean testSoftStrike;
    private boolean testHardStrike;
    private boolean testFlam;
    private boolean testChoke;


    private int assignment;
    private int softVelocity;
    private int hardVelocity;
    private long flamTime;
    private long chokeTime;



    private String infoText;



    /**
     * Constructor for the test window.
     * Automatically does a regular hit test to see what component is
     * going to be played.
     * @param testSoftStrike - True to do a soft strike test.
     * @param testHardStrike - True to do a hard strike test.
     * @param testFlam - True to do a flam test.
     * @param testChoke - True to do a choke test.
     */
    public TestWindow(boolean testSoftStrike, boolean testHardStrike, boolean testFlam, boolean testChoke)
    {
        testFinished = false;
        
        testHit = true;
        this.testSoftStrike = testSoftStrike;
        this.testHardStrike = testHardStrike;
        this.testFlam = testFlam;
        this.testChoke = testChoke;

        assignment = ApplicationProperties.EMPTY_VALUE;
        softVelocity = ApplicationProperties.EMPTY_VALUE;
        hardVelocity = ApplicationProperties.EMPTY_VALUE;
        flamTime = ApplicationProperties.EMPTY_VALUE;
        chokeTime = ApplicationProperties.EMPTY_VALUE;

        infoText = ApplicationProperties.EMPTY_STRING;

        initComponents();
    }

    /**
     * Gets the main component of this class.
     * @return the JPanel, this.
     */
    public JPanel getMainComponent()
    {
        return this;
    }

    /**
     * Starts the test itself.
     */
    public void constructTest()
    {
        new Thread(new Test()).start();
    }

    /**
     * Synchronized method to add to the info text.
     * @param infoText - new text to add to the current info text.
     */
    public synchronized void addInfoText(String infoText)
    {
        this.infoText += infoText + "\n";
    }

    /**
     * Synchronized method to get what the info text is.
     * @return the current info text.
     */
    public synchronized String getInfoText()
    {
        return infoText;
    }

    

    /**
     * Because infoText is a swing component, we can only
     * update it through SwingUtilities.
     */
    public void updateInfoText()
    {
        SwingUtilities.invokeLater(new Runnable()
        {

            public void run()
            {
                infoTextArea.setText(getInfoText());
            }
        });
    }

    /**
     * Cancel button was pressed.
     * Close the window, and tell the calling window the
     * test was never finished.
     */
    private void cancelButtonPressed()
    {
        testFinished = false;
        closeWindow();
    }


    /**
     * Closes the window.
     */
    public void closeWindow()
    {
        Window window = SwingUtilities.getWindowAncestor(this);
        window.dispose();
    }


    /**
     * Returns whether the test was finished or not.
     * @return True if the test finishes, false otherwise.
     */
    public boolean getTestFinished()
    {
        return testFinished;
    }

    /**
     * Returns the assignment for this component.
     * @return the assignment for this component.
     */
    public int getAssigment()
    {
        return assignment;
    }

    /**
     * Returns the soft velocity value for this component.
     * @return the soft velocity value for this component.
     */
    public int getSoftVelocity()
    {
        return softVelocity;
    }

    /**
     * Returns the hard velocity value for this component.
     * @return the hard velocity value for this component.
     */
    public int getHardVelocity()
    {
        return hardVelocity;
    }

    /**
     * Returns the flam time for this component.
     * @return the flam time for this component.
     */
    public long getFlamTime()
    {
        return flamTime;
    }

    /**
     * Returns the choke time for this component.
     * @return the choke time for this component.
     */
    public long getChokeTime()
    {
        return chokeTime;
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        cancelButton = new javax.swing.JButton();
        jScrollPane1 = new javax.swing.JScrollPane();
        infoTextArea = new javax.swing.JTextArea();
        completeButton = new javax.swing.JButton();

        cancelButton.setFont(new java.awt.Font("Bitstream Vera Sans", 1, 12)); // NOI18N
        cancelButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/img/cancel.png"))); // NOI18N
        cancelButton.setText("Cancel");
        cancelButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cancelButtonActionPerformed(evt);
            }
        });

        infoTextArea.setColumns(20);
        infoTextArea.setFont(new java.awt.Font("Bitstream Vera Sans", 1, 12)); // NOI18N
        infoTextArea.setLineWrap(true);
        infoTextArea.setRows(5);
        infoTextArea.setText("Perform the following instructions:");
        infoTextArea.setWrapStyleWord(true);
        jScrollPane1.setViewportView(infoTextArea);

        completeButton.setFont(new java.awt.Font("Bitstream Vera Sans", 1, 12)); // NOI18N
        completeButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/img/ok.png"))); // NOI18N
        completeButton.setText("Complete Configuration");
        completeButton.setEnabled(false);
        completeButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                completeButtonActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 373, Short.MAX_VALUE)
                    .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
                        .addComponent(completeButton)
                        .addGap(18, 18, 18)
                        .addComponent(cancelButton)))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 165, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addGap(18, 18, 18)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(completeButton)
                    .addComponent(cancelButton))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
    }// </editor-fold>//GEN-END:initComponents

    private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_cancelButtonActionPerformed
    {//GEN-HEADEREND:event_cancelButtonActionPerformed
        cancelButtonPressed();
}//GEN-LAST:event_cancelButtonActionPerformed

    private void completeButtonActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_completeButtonActionPerformed
    {//GEN-HEADEREND:event_completeButtonActionPerformed
        closeWindow();
}//GEN-LAST:event_completeButtonActionPerformed


    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton cancelButton;
    private javax.swing.JButton completeButton;
    private javax.swing.JTextArea infoTextArea;
    private javax.swing.JScrollPane jScrollPane1;
    // End of variables declaration//GEN-END:variables


    /**
     * Sub-class: Test
     * Description: Test just runs the tests that were requested
     * through a thread.
     * @author: et
     */
    class Test implements Runnable, MidiObserver
    {
        private MidiSubject midiController;
        private int lastAssignment;
        private int lastVelocity;
        private boolean hitOn;
        private boolean hitOff;
        private boolean touch;
        private long touchTime;
        private long hitTimeOn;
        private long hitTimeOff;

        public Test()
        {
            lastAssignment = ApplicationProperties.EMPTY_VALUE;
            lastVelocity = 0;

            hitOn = false;
            hitOff = false;
            touch = false;

            hitTimeOn = 0;
            hitTimeOff = 0;
            touchTime = 0;


            midiController = new MidiController();
            midiController.refreshDevice();
            midiController.registerObserver(this);
        }

        /**
         * Goes through all the requested tests,
         * each one should be self-explanatory from the texts.
         */
        public void run()
        {
            if (testHit)
            {
                addInfoText("Hit the component you are trying to configure.");
                updateInfoText();

                // Wait for the user to hit something.
                while (!getHitOn())
                {  
                }

                assignment = getLastAssignment();

                addInfoText("You are configuring component: " + assignment + "\n");
                updateInfoText();
            }

            setHitOn(false);
            setHitOff(false);
            setTouch(false);

            if (testSoftStrike)
            {
                addInfoText("Hit the same component lightly (like a grace hit).");
                updateInfoText();
                
                while (testSoftStrike)
                {
                    while (!getHitOn())
                    {}
                    
                    if (getLastAssignment() == assignment)
                    {
                        softVelocity = getLastVelocity();
                        testSoftStrike = false;
                        addInfoText("You struck component " + assignment + " lightly with a velocity of " + softVelocity + "\n");
                        updateInfoText();
                    }
                    else
                    {
                        addInfoText("You didn't hit the correct component. Please try again.");
                        updateInfoText();
                        setHitOn(false);
                    }
                }
            }

            setHitOn(false);
            setHitOff(false);
            setTouch(false);

            if (testHardStrike)
            {
                addInfoText("Hit the same component hard (like an accent note).");
                updateInfoText();

                while (testHardStrike)
                {
                    while (!getHitOn())
                    {}

                    if (getLastAssignment() == assignment)
                    {
                        hardVelocity = getLastVelocity();
                        testHardStrike = false;
                        addInfoText("You struck component " + assignment + " hard with a velocity of " + hardVelocity + "\n");
                    }
                    else
                    {
                        addInfoText("You didn't hit the correct component. Please try again.");
                        updateInfoText();
                        setHitOn(false);
                    }
                }
            }

            setHitOn(false);
            setHitOff(false);
            setTouch(false);


            if (testFlam)
            {
                addInfoText("Perform a flam (a grace hit followed by a regular hit) on the same component.");
                updateInfoText();
                while (testFlam)
                {
                    setHitOn(false);
                    
                    while (!getHitOn())
                    {
                        
                    }

                    if (getLastAssignment() != assignment)
                    {
                        addInfoText("You didn't hit the correct component. Please try again.");
                        updateInfoText();
                        continue;
                    }
                    flamTime = getHitTimeOn();


                    setHitOn(false);


                    while (!getHitOn())
                    {
                    }

                    if (getLastAssignment() == assignment)
                    {
                        flamTime = getHitTimeOn() - flamTime;
                        testFlam = false;
                        addInfoText("You flammed on component " + assignment +
                                " with a flam time (time between hits) of: " + flamTime + " milliseconds.\n\n");
                        updateInfoText();
                    }
                    else
                    {
                        addInfoText("You didn't hit the correct component. Please try again.");
                        updateInfoText();
                        continue;
                    }

                }
            }

            setHitOn(false);
            setHitOff(false);
            setTouch(false);

            if (testChoke)
            {
                addInfoText("Hit the component, then choke it.");
                updateInfoText();
                while (testChoke)
                {
                    while (!getHitOn())
                    {
                    }
                    if (lastAssignment != assignment)
                    {
                        addInfoText("You didn't hit the correct component. Please try again.");
                        updateInfoText();
                        setHitOn(false);
                        continue;
                    }
                    chokeTime = getHitTimeOn();
                    while (!touch)
                    {
                    }
                    if (lastAssignment == assignment)
                    {
                        testChoke = false;
                    }
                    else
                    {
                        addInfoText("You didn't choke the correct component. Please try again.");
                        updateInfoText();
                        setTouch(false);
                        chokeTime = getTouchTime() - chokeTime;
                    }
                }
            }

            // If we got here, then all the tests passed.
            midiController.removeObserver(this);
            addInfoText("All tests completed.");
            updateInfoText();
            testFinished = true;
            completeButton.setEnabled(true);

        }


        /**
         * Received a hit on message, update all the fields.
         * @param assignment - the component that was hit.
         * @param velocity - the velocity at which the component was hit at.
         */
        public void hitOn(int assignment, int velocity)
        {
            System.out.println("GOT HIT ON");
            setLastAssignment(assignment);
            setLastVelocity(velocity);
            setHitTimeOn(System.currentTimeMillis());
            setHitOn(true);
        }

        /**
         * Received a hit off message, update all the fields.
         * @param assignment - the component that was released.
         * @param velocity - the velocity at which the component was released at.
         */
        public void hitOff(int assignment, int velocity)
        {
            System.out.println("GOT HIT OFF");
            setHitTimeOff(System.currentTimeMillis());
            setHitOff(true);
            
        }

        /**
         * Received a touch message, update all the fields.
         * @param assignment - the component that was touched.
         * @param velocity - the velocity at which the component was touched at.
         */
        public void touch(int assignment, int velocity)
        {
            System.out.println("GOT TOUCH");
            setTouchTime(System.currentTimeMillis());
            setTouch(true);
        }

        /**
         * Synchronized method to get the last component that
         * received a message.
         * @return the last assignment.
         */
        public synchronized int getLastAssignment()
        {
            return lastAssignment;
        }

        /**
         * Synchronized method to set the last component that
         * received a message.
         * @param lastAssignment - the last assignment.
         */
        public synchronized void setLastAssignment(int lastAssignment)
        {
            this.lastAssignment = lastAssignment;
        }

        /**
         * Synchronized method to get the last velocity
         * that was hit.
         * @return the last velocity.
         */
        public synchronized int getLastVelocity()
        {
            return lastVelocity;
        }

        /**
         * Synchronized method to set the last velocity that
         * a component was hit at.
         * @param lastVelocity - the last velocity.
         */
        public synchronized void setLastVelocity(int lastVelocity)
        {
            this.lastVelocity = lastVelocity;
        }

        /**
         * Syncronized method to get whether there was a hit on message.
         * @return true if there was hit on message.
         */
        public synchronized boolean getHitOn()
        {
            return hitOn;
        }

        public synchronized void setHitOn(boolean hitOn)
        {
            this.hitOn = hitOn;
        }

        /**
         * Syncronized method to get whether there was a hit off message.
         * @return true if there was hit off message.
         */
        public synchronized boolean getHitOff()
        {
            return hitOff;
        }

        public synchronized void setHitOff(boolean hitOff)
        {
            this.hitOff = hitOff;
        }

        /**
         * Syncronized method to get whether there was a touch message.
         * @return true if there was touch message.
         */
        public synchronized boolean getTouch()
        {
            return touch;
        }

        public synchronized void setTouch(boolean touch)
        {
            this.touch = touch;
        }
        
        public synchronized long getTouchTime()
        {
            return touchTime;
        }

        public synchronized void setTouchTime(long touchTime)
        {
            this.touchTime = touchTime;
        }

        public synchronized long getHitTimeOn()
        {
            return hitTimeOn;
        }

        public synchronized void setHitTimeOn(long hitTimeOn)
        {
            this.hitTimeOn = hitTimeOn;
        }

        public synchronized long getHitTimeOff()
        {
            return hitTimeOff;
        }

        public synchronized void setHitTimeOff(long hitTimeOff)
        {
            this.hitTimeOff = hitTimeOff;
        }


    }


}
